package com.suning.sawp.service.util;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;

import com.alibaba.dubbo.common.utils.CollectionUtils;
import com.google.common.collect.Lists;
import com.mongodb.WriteResult;
import com.suning.sawp.cache.RedisClient;
import com.suning.sawp.constants.ConfigConstants;
import com.suning.sawp.constants.ErrorCodeConstants;
import com.suning.sawp.constants.ExportConstants;
import com.suning.sawp.dto.common.ReturnMsg;
import com.suning.sawp.dto.oss.ExportTaskDto;
import com.suning.sawp.intf.model.ModelConstant;
import com.suning.sawp.intf.util.OSSFileService;
import com.suning.sawp.po.util.OSSFileResult;

/**
 * 
 * 〈一句话功能简述〉用于异步导出文件的工具类<br>
 * 〈功能详细描述〉
 *
 * @author 17033093
 * @see [相关类/方法]（可选）
 * @since [产品/模块版本] （可选）
 */
@Component
public class ExportFileUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ExportFileUtil.class);

    @Resource
    OSSFileService ossFileService;

    @Resource
    MongoTemplate mongoTemplate;

    @Autowired
    RedisClient redisClient;

    /**
     * 文件上传成功
     */
    private static final String FILE_UPLOAD_SUCCESS = "1";

    /**
     * 未删除
     */
    private static final int DELETE_NOT = 0;

    /**
     * 已删除
     */
    private static final int DELETE_YES = 1;

    /**
     * Mongo查询起始位置
     */
    private static int MONGO_START_INDEX = 0;

    /**
     * 查询条数
     */
    private static int MONGO_LIMIT = 10;

    /**
     * 任务正在执行
     */
    private static String TASK_STATE_RUNNING = "1";

    /**
     * 任务已停止
     */
    private static String TASK_STATE_STOP = "0";

    /**
     * 
     * 功能描述: 添加导出任务<br>
     * 〈功能详细描述〉
     *
     * @param staffId
     * @param className
     * @param methodName 方法名在类中必须唯一
     * @param params
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    @SuppressWarnings("rawtypes")
    public ReturnMsg saveExportTask(String staffId, String className, String methodName, List<Object> params) {
        ReturnMsg<?> ret = new ReturnMsg();
        if (StringUtils.isBlank(staffId)) {
            LOGGER.error("saveExportTask. StaffId is null");
            ret.setError(ExportConstants.ERR_E003, ExportConstants.MODEL_EXPORT_ERR_MAP);
            return ret;
        }
        if (StringUtils.isBlank(className)) {
            LOGGER.error("saveExportTask.ClassName is null");
            ret.setError(ExportConstants.ERR_E004, ExportConstants.MODEL_EXPORT_ERR_MAP);
            return ret;
        }
        if (StringUtils.isBlank(methodName)) {
            LOGGER.error("saveExportTask.MethodName is null");
            ret.setError(ExportConstants.ERR_E005, ExportConstants.MODEL_EXPORT_ERR_MAP);
            return ret;
        }
        if (CollectionUtils.isEmpty(params)) {
            LOGGER.error("saveExportTask. Params is null");
            ret.setError(ExportConstants.ERR_E006, ExportConstants.MODEL_EXPORT_ERR_MAP);
            return ret;
        }
        // 根据文件名和方法名获取参数类型
        try {
            Class thisClass = Class.forName(className);
            Method thisMethod = null;
            Method[] methods = thisClass.getMethods();
            for (Method method : methods) {
                if (methodName.equals(method.getName())) {
                    thisMethod = method;
                    break;
                }
            }
            if (null == thisMethod) {
                LOGGER.error("saveExportTask. This class does not find this method");
                ret.setError(ExportConstants.ERR_E005, ExportConstants.MODEL_EXPORT_ERR_MAP);
                return ret;
            }
            Class<?>[] paramClassTypes = thisMethod.getParameterTypes();
            List<String> paramsType = new ArrayList<String>();
            for (Class<?> classType : paramClassTypes) {
                paramsType.add(classType.getName());
            }
            // 参数值个数和参数类型个数不匹配提示异常
            if (params.size() != paramsType.size()) {
                LOGGER.error("saveExportTask. params`s size is not equals paramsType`s size");
                ret.setError(ExportConstants.ERR_E007, ExportConstants.MODEL_EXPORT_ERR_MAP);
                return ret;
            }
            ExportTaskDto dto = new ExportTaskDto(staffId, className, methodName, params, paramsType);
            mongoTemplate.save(dto);
        } catch (ClassNotFoundException e) {
            LOGGER.error("saveExportTask failed.", e);
        } catch (Exception e) {
            LOGGER.error("saveExportTask failed.", e);
        }
        return ret;
    }

    /**
     * 
     * 功能描述: 上传文件并发送邮件提醒<br>
     * 〈功能详细描述〉
     *
     * @param fileName
     * @param staffId
     * @param file
     * @param exportTip 如样机有效性1、样机有效性2
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public void uploadFileAndSendEmail(String fileName, String staffId, File file, String exportTip) {
        if (null != file) {
            OSSFileResult rsp = ossFileService.upload2OSSByFile(ExportConstants.OSSFileParams.BUCKET_EXPORT, fileName,
                    file);
            if (null != rsp && FILE_UPLOAD_SUCCESS.equals(rsp.getRetFlag())) {
                // 上传成功发送邮件提醒
                EmailUtil.sendEmail(staffId, rsp.getFileUrl(), exportTip);
            } else {
                // 失败不发送邮件
                LOGGER.error("uploadFileAndSendEmail failed. Rsp:{}", rsp);
            }
            // 删除临时文件
            if (!file.delete()) {
                LOGGER.error("uploadFileAndSendEmail delete temp file error");
            }
        } else {
            LOGGER.error("createFileAndSendEmail failed. File is null");
        }
    }
    
    /**
     * 
     * 功能描述: 生成文件并发送邮件提醒<br>
     * 〈功能详细描述〉
     *
     * @param staffId 工号
     * @param titles 文件列头
     * @param allDatas 导出数据
     * @param exportTip 导出类型+index，如样机有效性1、样机有效性2
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public void createCsvFileAndSendEmail(String staffId, String[] titles, List<List<String>> allDatas, String exportTip) {
        String fileName = String.valueOf(new Date().getTime());
        String cycleTime = ExportConstants.OSSFileParams.FILE_CYCLE_TIME;
        this.createCsvFileAndSendEmail(fileName, staffId, titles, allDatas, exportTip, cycleTime);
    }
    
    /**
     * 
     * 功能描述: 生成文件并发送邮件提醒<br>
     * 〈功能详细描述〉
     *
     * @param fileName 文件名
     * @param staffId 工号
     * @param titles 文件列头
     * @param allDatas 导出数据
     * @param exportTip 导出类型+index，如样机有效性1、样机有效性2
     * @param cycleTime 有效期，单位天(1-365), 如ExportConstants.OSSFileParams.FILE_CYCLE_TIME
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public void createCsvFileAndSendEmail(String fileName, String staffId, String[] titles, List<List<String>> allDatas,
            String exportTip, String cycleTime) {
        LOGGER.info("createCsvFileAndSendEmail params. fileName:{}, staffId:{}, titles:{}, exportTip:{}, cycleTime:{}",
                fileName, staffId, titles, exportTip, cycleTime);
        try {
            if (StringUtils.isBlank(fileName) || StringUtils.isBlank(fileName) || null == titles
                    || CollectionUtils.isEmpty(allDatas) || StringUtils.isBlank(cycleTime)) {
                LOGGER.error("createCsvFileAndSendEmail. Params is illegal");
                return;
            }
            // 数据切割，五万生成一个文件
            int expNum = 1;
            for (List<List<String>> datas : Lists.partition(allDatas, getExptLimit())) {
                // 若需要数据切割，对第二个文件开始设置一个编号
                String flag = expNum == ModelConstant.NUM_ONE ? "" : (expNum + "-");
                fileName += "-" + flag + new Date().getTime() + "@";
                File file = CsvUtils.exportFile(fileName, titles, datas);
                if (null != file) {
                    OSSFileResult rsp = ossFileService.upload2OSSByFile(ExportConstants.OSSFileParams.BUCKET_EXPORT,
                            fileName, file, cycleTime);
                    if (null != rsp && FILE_UPLOAD_SUCCESS.equals(rsp.getRetFlag())) {
                        // 上传成功发送邮件提醒
                        EmailUtil.sendEmail(staffId, rsp.getFileUrl(), exportTip);
                    } else {
                        // 失败不发送邮件
                        LOGGER.error("createCsvFileAndSendEmail failed. Rsp:{}", rsp);
                    }
                    // 删除临时文件
                    if (!file.delete()) {
                        LOGGER.error("createCsvFileAndSendEmail delete temp file error");
                    }
                } else {
                    LOGGER.error("createCsvFileAndSendEmail failed. File is null");
                }
            }
        } catch (IOException e) {
            LOGGER.error("createFileAndSendEmail failed.", e);
        } catch (Exception e) {
            LOGGER.error("createFileAndSendEmail failed.", e);
        }
    }
    
    public String createCsvFileAndSendEmail(String fileName, String[] titles, List<List<String>> allDatas,
            String exportTip, String cycleTime) {
        LOGGER.info("createCsvFileAndSendEmail params. fileName:{}, staffId:{}, titles:{}, exportTip:{}, cycleTime:{}",
                fileName, titles, exportTip, cycleTime);
        try {
            if (StringUtils.isBlank(fileName) || StringUtils.isBlank(fileName) || null == titles
                    || CollectionUtils.isEmpty(allDatas) || StringUtils.isBlank(cycleTime)) {
                LOGGER.error("createCsvFileAndSendEmail. Params is illegal");
                return null;
            }

            // 若需要数据切割，对第二个文件开始设置一个编号
            File file = CsvUtils.exportFile(fileName, titles, allDatas);
            if (null != file) {
                OSSFileResult rsp = ossFileService.upload2OSSByFile(ExportConstants.OSSFileParams.BUCKET_EXPORT,
                        fileName, file, cycleTime);
                if (null != rsp && FILE_UPLOAD_SUCCESS.equals(rsp.getRetFlag())) {
                    return rsp.getFileUrl();
                } else {
                    // 失败不发送邮件
                    LOGGER.error("createCsvFileAndSendEmail failed. Rsp:{}", rsp);
                }
                // 删除临时文件
                if (!file.delete()) {
                    LOGGER.error("createCsvFileAndSendEmail delete temp file error");
                }
            } else {
                LOGGER.error("createCsvFileAndSendEmail failed. File is null");
            }
        
        } catch (IOException e) {
            LOGGER.error("createFileAndSendEmail failed.", e);
        } catch (Exception e) {
            LOGGER.error("createFileAndSendEmail failed.", e);
        }
        return null;
    }
    
    private int getExptLimit() {
        String limit = SCMConfigUtil.getConfig(ConfigConstants.MODEL_MANAGE_EXPORT_CSV_SIZE,
                ConfigConstants.DEFAULT_MODEL_MANAGE_EXPORT_CSV_SIZE);
        return Integer.valueOf(limit).intValue();
    }

    /**
     * 
     * 功能描述: 循环执行导出任务<br>
     * 〈功能详细描述〉
     *
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public void loopInvokeExportTask() {
        LOGGER.info("loopInvokeExportTask. start");
        // 从redis中获取标志 -> 若为1标示任务正在执行，直接跳过；其他情况则执行
        String exportTaskState = redisClient.get(ExportConstants.EXPORT_LOOP_TASK_STATE);
        if (TASK_STATE_RUNNING.equals(exportTaskState)) {
            LOGGER.warn("loopInvokeExportTask. Task is Running.");
            return;
        }

        // 查询10个导出任务
        Query query = new Query();
        query.with(new Sort(new Order(Direction.ASC, ExportTaskDto.Attributes.CREATE_TIME)));
        query.skip(MONGO_START_INDEX).limit(MONGO_LIMIT);
        Criteria criteria = new Criteria();
        criteria.and(ExportTaskDto.Attributes.DELETE_FLAG).is(DELETE_NOT);
        query.addCriteria(criteria);
        List<ExportTaskDto> taskList = mongoTemplate.find(query, ExportTaskDto.class);
        if (CollectionUtils.isNotEmpty(taskList)) {
            LOGGER.info("loopInvokeExportTask. Task begin. TaskList:{}", taskList);
            // 修改redis中的值为1，任务进入执行状态
            redisClient.set(ExportConstants.EXPORT_LOOP_TASK_STATE, TASK_STATE_RUNNING);
            redisClient.expireAt(ExportConstants.EXPORT_LOOP_TASK_STATE, redisExpireTime());
            try {
                for (ExportTaskDto dto : taskList) {
                    if (null == dto) {
                        continue;
                    }
                    // 执行任务
                    ReturnMsg<String> exeTaskRet = this.executeExportTask(dto);
                    LOGGER.info("loopInvokeExportTask. exeTaskResult:{}", exeTaskRet);
                    // 不管成功失败，修改mongo删除状态
                    this.updateTaskDelFlagMongo(dto);
                }
            } catch (Exception e) {
                LOGGER.error("loopInvokeExportTask loop failed.", e);
            }
            // 修改redis中的值为0，标示任务执行结束
            redisClient.set(ExportConstants.EXPORT_LOOP_TASK_STATE, TASK_STATE_STOP);
            redisClient.expireAt(ExportConstants.EXPORT_LOOP_TASK_STATE, redisExpireTime());
            LOGGER.info("loopInvokeExportTask. Task End.");
        } else {
            LOGGER.warn("loopInvokeExportTask. TaskList is null");
        }

    }

    /**
     * 
     * 功能描述: 执行导出任务<br>
     * 〈功能详细描述〉
     *
     * @param task
     * @return
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public ReturnMsg<String> executeExportTask(ExportTaskDto task) {
        ReturnMsg<String> ret = new ReturnMsg<String>();

        String className = task.getClassName();
        String methodName = task.getMethodName();
        List<Object> params = task.getParams();
        List<String> paramsType = task.getParamsType();

        // 参数校验
        if (StringUtils.isBlank(className) || StringUtils.isBlank(methodName) || null == params || null == paramsType) {
            LOGGER.error("executeExportTask. param is null. className:{}, methodName:{}, params:{}, paramsType:{}",
                    className, methodName, params, paramsType);
            ret.setError(ErrorCodeConstants.EXPORT_ERR_E001);
            return ret;
        }

        try {
            Object[] args = new Object[paramsType.size()];
            for (int i = 0; i < args.length; i++) {
                Class<?> obj = Class.forName(paramsType.get(i));
                if (null != params.get(i)) {
                    args[i] = obj.cast(params.get(i));
                } else {
                    args[i] = null;
                }
            }

            @SuppressWarnings("rawtypes")
            Class thisClass = Class.forName(className);
            Method thisMethod = null;
            Method[] methods = thisClass.getMethods();
            for (Method method : methods) {
                if (methodName.equals(method.getName())) {
                    thisMethod = method;
                    break;
                }
            }

            if (null != thisMethod) {
                Object bean = SpringContextUtils.getBeanById(toLowerCaseFirstOne(className));
                if (null != bean) {
                    thisMethod.invoke(bean, args);
                }
            } else {
                LOGGER.error("executeExportTask. not found this method.ExportTask:{}", task);
                ret.setError(ErrorCodeConstants.EXPORT_ERR_E007);
            }
        } catch (ClassNotFoundException e) {
            LOGGER.error("executeExportTask failed.", e);
            ret.setError(ErrorCodeConstants.EXPORT_ERR_E003);
        } catch (IllegalAccessException e) {
            LOGGER.error("executeExportTask failed.", e);
            ret.setError(ErrorCodeConstants.EXPORT_ERR_E003);
        } catch (IllegalArgumentException e) {
            LOGGER.error("executeExportTask failed.", e);
            ret.setError(ErrorCodeConstants.EXPORT_ERR_E003);
        } catch (InvocationTargetException e) {
            LOGGER.error("executeExportTask failed.", e);
            ret.setError(ErrorCodeConstants.EXPORT_ERR_E003);
        } catch (Exception e) {
            LOGGER.error("executeExportTask failed.", e);
            ret.setError(ErrorCodeConstants.EXPORT_ERR_E003);
        }

        return ret;
    }

    /**
     * 
     * 功能描述: 更新导出任务删除字段状态<br>
     * 〈功能详细描述〉
     *
     * @param task
     * @see [相关类/方法](可选)
     * @since [产品/模块版本](可选)
     */
    public void updateTaskDelFlagMongo(ExportTaskDto task) {
        Query queryHorAct = new Query();
        Criteria criteria = new Criteria();
        criteria.and(ExportTaskDto.Attributes.EXPORT_TASK_ID).is(task.getId());
        queryHorAct.addCriteria(criteria);
        Update update = new Update();
        update.set(ExportTaskDto.Attributes.DELETE_FLAG, DELETE_YES);
        WriteResult rsp = mongoTemplate.updateFirst(queryHorAct, update, ExportTaskDto.class);
        LOGGER.info("updateExportTaskMongo. TaskId:{}, Rsp:{}", task.getId(), rsp);
    }

    /*
     * 一天有效期
     */
    private long redisExpireTime() {
        // 取当前时间的小时数和分钟数
        Date tomorrowZeroPoint = DateUtils.getNextDay(DateUtils.getNowZeroPoint(), 1);
        return tomorrowZeroPoint.getTime() / 1000;
    }

    /*
     * 文件名路径解析，并获取首字母转小写
     */
    private static String toLowerCaseFirstOne(String word) {
        if (StringUtils.isBlank(word)) {
            return "";
        }
        String[] w = word.split("\\.");
        word = w[w.length - 1];
        if (Character.isLowerCase(word.charAt(0))) {
            return word;
        } else {
            return (new StringBuilder()).append(Character.toLowerCase(word.charAt(0))).append(word.substring(1))
                    .toString();
        }
    }
}
